﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.IO.IsolatedStorage;
using System.Linq;
using System.Windows;
using System.Xml;
using System.Xml.Linq;
using System.Threading;


namespace WeatherFrame
{
    public class WeatherModel : INotifyPropertyChanged
    {
        public WeatherModel()
        {
            this.Items = new ObservableCollection<object>();
            // set up locations
            this.Locations = new ObservableCollection<Location>();
            // Set up first day
            Items.Add(new WeatherItem { Parent = this });
//            SelectedItem = Items[0];
        }

        public void AddDefaultLocations()
        {
            Locations.Add(new Location { Name = "London", Country = "United Kingdom", Latitude = 51.5,Longitude = -0.12});
            Locations.Add(new Location { Name = "New York", Region = "New York", Country = "United States of America", Latitude=40.71, Longitude=-74.01 });
            Locations.Add(new Location { Name = "Tokyo", Region = "Tokyo", Country = "Japan", Latitude=35.69, Longitude=139.75 });
            Locations.Add(new Location { Name = "Sydney", Region = "New South Wales", Country = "Australia", Latitude = -33.88, Longitude = 151.22 });
        }

        public List<WeatherItem> Days { 
            get { return Items.OfType<WeatherItem>().ToList(); }
            set
            {
                var dispatcher = System.Windows.Deployment.Current.Dispatcher;
                foreach (var wi in Days) { wi.Clear(); wi.Location = value[0].Location; }
                dispatcher.BeginInvoke(() =>
                {
                    while (Items.Count < value.Count + 1) Items.Insert(1,new WeatherItem());
                    while (Items.Count > value.Count + 1) Items.RemoveAt(0);//Items.Count - 1);

                    int ct = /*k + 1;*/ 0;
                    foreach (WeatherItem wi in value)
                    {
                        ((WeatherItem)Items[ct]).SetTo(wi);
                        ct++; if (ct >= Items.Count) ct = 0;
                    }
                    if (Items[Items.Count - 1] != this) Items[Items.Count - 1] = this;
                });
            }
        }

        public ObservableCollection<Location> Locations { get; private set; }

        public ObservableCollection<object> Items
        {
            get;
            private set;
        }


        public ImageIndex ImageIndex = new ImageIndex();

        bool loaded = false;
        internal void LoadData()
        {
            if (loaded) return;
            loaded = true;
            // Read in the image index
            BackgroundWorker imageIndexWorker = new BackgroundWorker { };
            imageIndexWorker.DoWork += new DoWorkEventHandler(ImageIndexWorker_DoWork);
            imageIndexWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(imageIndexWorker_RunWorkerCompleted);
            imageIndexWorker.RunWorkerAsync();
            //ReadImageIndex();

            gotoSelectedLocation = false;
            // Read in the current settings
            ReadSettings();
            if (Locations.Count == 0) AddDefaultLocations();

            CheckAllowAccess(true);
            // Get the weather
            
            if (selectedLocation == null)
            {
                SelectedLocation = Locations[0];
            }
            gotoSelectedLocation = true;
        }

        void ImageIndexWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            ReadImageIndex();
        }

        void imageIndexWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            foreach (Location loc in Locations)
            {
                if (!loc.HasWeather) continue;
                foreach (var wi in loc.days)
                {
                    wi.SetImageFromCondition();
                }
            }
        }

        private void ReadImageIndex()
        {
            var s = App.GetResourceStream(new Uri("/WeatherFrame;component/ImageIndex.xml", UriKind.Relative));
            using (var str = s.Stream)
            {
                ImageIndex.Read(str);
            }
        }

        const string filename = "settings.txt";
        private void ReadSettings()
        {
            try
            {
                IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication();
                if (!isoStore.FileExists(filename)) return;
                using (var str = isoStore.OpenFile(filename, FileMode.Open))
                {
                    Read(str);
                }
            }
            catch (Exception) { }
        }

        internal void WriteSettings()
        {
            try
            {
                IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication();
                using (var str = isoStore.OpenFile(filename, FileMode.Create))
                {
                    Write(str);
                }
            }
            catch (Exception) { }
        }

        private void Read(Stream s)
        {            
            var xmlRoot = XElement.Load(s);
            var settingsNode = xmlRoot.DescendantsAndSelf("settings").First();
            UseCelsius = (bool)settingsNode.Attribute("UseCelsius");
            try
            {
                // note: set field not property
                allowAccessToLocation = (bool)settingsNode.Attribute("AllowAccessToLocation");
            }
            catch (Exception) { }
            Location selLoc = null;
            foreach (var locNode in settingsNode.Elements("location"))
            {
                var loc = new Location{};
                try
                {
                    loc.IsHere = (bool)locNode.Attribute("isHere");
                    if (!loc.IsHere)
                    {
                        loc.Name = (string)locNode.Attribute("name"); 
                        loc.Latitude = (double)locNode.Attribute("latitude");
                        loc.Longitude = (double)locNode.Attribute("longitude");
                        loc.Region = (string)locNode.Attribute("region");
                        loc.Country = (string)locNode.Attribute("Country"); 
                    }
                }
                catch (Exception) { }
                var selected = (bool)locNode.Attribute("selected");
                
                if (selected) selLoc = loc;
                Locations.Add(loc);
            }
            SelectedLocation = selLoc;
        }

        private void Write(Stream s)
        {
            XmlWriterSettings xws = new XmlWriterSettings{ Indent = true};
            using (XmlWriter x = XmlWriter.Create(s, xws))
            {
                x.WriteStartDocument();
                x.WriteStartElement("settings");
                x.WriteAttributeString("UseCelsius", UseCelsius.ToString());
                x.WriteAttributeString("AllowAccessToLocation", AllowAccessToLocation.ToString());
                foreach (var l in Locations)
                {
                    x.WriteStartElement("location");
                    x.WriteAttributeString("isHere", l.IsHere.ToString());
                    if (!l.IsHere)
                    {
                        x.WriteAttributeString("name", l.Name);
                        x.WriteAttributeString("latitude", l.Latitude.ToString());
                        x.WriteAttributeString("longitude", l.Longitude.ToString());
                        if (l.Region!=null) x.WriteAttributeString("region", l.Region);
                        if (l.Country != null) x.WriteAttributeString("country", l.Country);
                    }
                    x.WriteAttributeString("selected", (l == selectedLocation).ToString());
                    x.WriteEndElement();
                }
                x.WriteEndElement();
                x.WriteEndDocument();
            }
        }


        internal void TryGetWeather()
        {
            service.GetWeatherForecastAsync(selectedLocation);
        }

        internal WeatherService service = new WeatherService();      

        bool useCelsius = true;
        public bool UseCelsius
        {
            get { return useCelsius; }
            set
            {
                useCelsius = value;
                foreach (var it in Days) it.OnTempChanged();
                NotifyPropertyChanged("UseCelsius");
            }
        }

        bool allowAccessToLocation = true;
        public bool AllowAccessToLocation
        {
            get {
                return allowAccessToLocation;
            }

            set
            {
                allowAccessToLocation = value;
                CheckAllowAccess(false);
                NotifyPropertyChanged("AllowAccessToLocation");
            }
        }

        void CheckAllowAccess(bool inThisThread)
        {
            bool hasHere = false;
            if  (Locations.Count > 0) hasHere = Locations[0].IsHere;
            if (hasHere == allowAccessToLocation) return;
            if (allowAccessToLocation)
            {
                AddLocation(new Location { IsHere = true },inThisThread);
            }
            else
            {
                RemoveLocation(Locations[0], inThisThread);
            }
        }

        bool showInfo = false;
        public bool ShowInfo
        {
            get { return showInfo; }
            set
            {
                showInfo = value;
                NotifyPropertyChanged("ShowInfo");
            }
        }

        bool isEditingLocations = false;
        public bool IsEditingLocations
        {
            get { return isEditingLocations; }
            set
            {
                isEditingLocations = value;
                NotifyPropertyChanged("IsEditingLocations");
            }
        }  

        LocationState locationState = LocationState.Normal;
        public LocationState LocationState
        {
            get { return locationState; }
            set
            {
                locationState = value;
                NotifyPropertyChanged("LocationState");
            }
        }


        public event PropertyChangedEventHandler PropertyChanged;
        internal void NotifyPropertyChanged(String propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler == null) return;
            var dispatcher = System.Windows.Deployment.Current.Dispatcher;
            dispatcher.BeginInvoke(() =>
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            });
        }

        #region Location handling (selected,add,remove)

        Location selectedLocation;
        bool gotoSelectedLocation = true;
        public Location SelectedLocation
        {
            get { return selectedLocation; }
            set
            {
                if (selectedLocation == value) return;
                selectedLocation = value;
                NotifyPropertyChanged("SelectedLocation");
                foreach (var wi in selectedLocation.Days) wi.Parent = this;
                Days = selectedLocation.Days;
                if (gotoSelectedLocation)
                {
                    SelectedItem = Items[0];                
                }

                if (!selectedLocation.HasWeather)
                {
                    TryGetWeather();
                }
            }
        }

        object selectedItem;
        public object SelectedItem
        {
            get { return selectedItem; }
            set
            {
                selectedItem = value;
                NotifyPropertyChanged("SelectedItem");
            }
        }

        internal void AddLocation(Location loc, bool inThisThread)
        {
            var addAction = new Action( () =>
            {
                int insertAt = 0;
                if (!loc.IsHere)
                {
                    for (int i = 0; i < Locations.Count; i++)
                    {
                        if (Locations[i].IsHere) continue;
                        if ((Locations[i].Latitude == loc.Latitude) &&
                            (Locations[i].Longitude == loc.Longitude))
                        {
                            Locations.RemoveAt(i);
                            break;
                        }
                    }
                    if ((Locations.Count > 0) && Locations[0].IsHere) insertAt = 1;
                }
                Locations.Insert(insertAt, loc);
                if (!loc.IsHere) SelectedLocation = loc;
            });
            if (inThisThread)
            {
                addAction();
            }
            else
            {
                var dispatcher = System.Windows.Deployment.Current.Dispatcher;
                dispatcher.BeginInvoke(addAction);
            }
        }

        internal void ConfirmLocation(Location location)
        {
            location.IsNew = false;
            LocationState = LocationState.Normal;
            AddLocation(location,false);            
        }

        internal void RemoveLocation(Location l, bool inThisThread)
        {
            if (Locations.Count <= 1) return;
            if (!l.IsHere && (Locations[0].IsHere && (Locations.Count<=2))) return;
            if (selectedLocation == l)
            {
                gotoSelectedLocation = false;
                SelectedLocation = Locations.First(loc=>loc!=l);
                gotoSelectedLocation = true;
            }
            if (inThisThread)
            {
                Locations.Remove(l);
            }
            else
            {
                var dispatcher = System.Windows.Deployment.Current.Dispatcher;
                dispatcher.BeginInvoke(() =>
                {
                    Locations.Remove(l);
                });
            }
        }        

        internal void BadLocation(Location location, string error = null)
        {
            if (error == null) error = "Unable to find location '" + location + "'";
            LocationState = LocationState.Editing;
            Error(error);
        }
        #endregion

        internal void Error(string msg)
        {
            var dispatcher = System.Windows.Deployment.Current.Dispatcher;
            dispatcher.BeginInvoke(() =>
            {
                MessageBox.Show(msg);
            });
        }

        internal void SearchLocation(string s)
        {
            App.ViewModel.LocationState = LocationState.Searching;
            service.SearchLocationAsync(s);
        }


        List<Location> searchResults;
        public List<Location> SearchResults
        {
            get { return searchResults; }
            set
            {
                searchResults = value;
                NotifyPropertyChanged("SearchResults");
            }
        }
    }

    public enum LocationState { Normal, Editing, Searching, SearchResults };
}